﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Text;
using System.IO;
using Northwind.Model;
using Northwind.Repositories;
using Northwind.Repositories.Entity;
using Lib.Web.Mvc.JQuery.JqGrid;
using jqGrid.Models;

namespace jqGrid.Controllers
{
    public class HomeController : Controller
    {
        #region Fields
        IProductsRepository _productsRepository;
        ISuppliersRepository _suppliersRepository;
        ICategoriesRepository _categoriesRepository;
        IOrdersRepository _ordersRepository;
        #endregion

        #region Constructors
        public HomeController()
            : this(new ProductsRepository(), new SuppliersRepository(), new CategoriesRepository(), new OrdersRepository())
        {
        }

        public HomeController(IProductsRepository productsRepository, ISuppliersRepository suppliersRepository, ICategoriesRepository categoriesRepository, IOrdersRepository ordersRepository)
        {
            _productsRepository = productsRepository;
            _suppliersRepository = suppliersRepository;
            _categoriesRepository = categoriesRepository;
            _ordersRepository = ordersRepository;
        }
        #endregion

        #region Actions
        public ActionResult Formatting()
        {
            return View();
        }

        public ActionResult Configuration()
        {
            return View();
        }

        public ActionResult DynamicScrolling()
        {
            return View();
        }

        public ActionResult Grouping()
        {
            return View();
        }

        /// <summary>
        /// Import configuration for jqGrid in json
        /// </summary>
        /// <param name="id">The jqGrid identifier</param>
        /// <returns>The jqGrid configuration in json format</returns>
        [Lib.Web.Mvc.NoCache]
        public JsonResult ImportConfiguration(string id)
        {
            JqGridConfiguration configuration = null;
            //Do we already have configuration in Session
            if (Session[id] != null)
                //If yes, the use it
                configuration = (JqGridConfiguration)Session[id];
            else
            {
                //If no, then create one
                configuration = new JqGridConfiguration()
                {
                    Settings = new JqGridOptions<ProductFormattedViewModel>(id)
                    {
                        ColumnsRemaping = new List<int>() { 0, 1, 2, 3, 4, 5, 6 },
                        RowsNumber = 10,
                        SortingName = "Id",
                        SortingOrder = JqGridSortingOrders.Asc,
                        ViewRecords = true
                    }
                };
            }

            //Those settings should be set always "just in case"
            configuration.Settings.Url = Url.Action("Products", "Home");
            configuration.Settings.DataType = JqGridDataTypes.Json;
            configuration.Settings.MethodType = JqGridMethodTypes.Post;
            configuration.Settings.Pager = true;

            return new JqGridJsonResult() { Data = configuration, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
        }

        /// <summary>
        /// Exports configuration for jqGrid
        /// </summary>
        /// <param name="configuration">The jqGrid configuration</param>
        /// <returns>An empty result</returns>
        [AcceptVerbs(HttpVerbs.Post)]
        public EmptyResult ExportConfiguration(JqGridConfiguration configuration)
        {
            //Store configuration in Session 
            Session[configuration.Settings.Id] = configuration;
            //Return empty result, it will be ignored
            return new EmptyResult();
        }

        public ActionResult SingleSearching()
        {
            return View();
        }

        public ActionResult ToolbarSearching()
        {
            return View();
        }

        public ActionResult CustomSearching()
        {
            return View();
        }

        public ActionResult AdvancedSearching()
        {
            return View();
        }

        public ActionResult AdvancedSearchingTemplates()
        {
            return View();
        }

        public ActionResult CellEditing()
        {
            return View();
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult UpdateProductProperty(CellEditingViewModel viewModel)
        {
            Product product = _productsRepository.FindByKey(viewModel.ProductId);
            switch (viewModel.PropertyName)
            {
                case "Name":
                    product.Name = (string)viewModel.PropertyValue;
                    break;
                case "CategoryId":
                    product.CategoryId = (int)viewModel.PropertyValue;
                    break;
                case "QuantityPerUnit":
                    product.QuantityPerUnit = (string)viewModel.PropertyValue;
                    break;
                case "UnitPrice":
                    product.UnitPrice = (decimal)viewModel.PropertyValue;
                    break;
            }

            try
            {
                _productsRepository.SaveChanges();
            }
            catch
            {
                return Json(false);
            }

            return Json(true);
        }

        public ActionResult InlineEditing()
        {
            return View();
        }

        public ActionResult FormEditing()
        {
            return View();
        }

        public ActionResult Loadonce()
        {
            return View();
        }

        public ActionResult JQueryUIIntegration()
        {
            return View();
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Products(JqGridRequest request, CustomSearchViewModel viewModel)
        {
            string filterExpression = String.Empty;
            if (request.Searching)
            {
                if (request.SearchingFilter != null)
                    filterExpression = GetFilter(request.SearchingFilter.SearchingName, request.SearchingFilter.SearchingOperator, request.SearchingFilter.SearchingValue);
                else if (request.SearchingFilters != null)
                {
                    StringBuilder filterExpressionBuilder = new StringBuilder();
                    string groupingOperatorToString = request.SearchingFilters.GroupingOperator.ToString();
                    foreach (JqGridRequestSearchingFilter searchingFilter in request.SearchingFilters.Filters)
                    {
                        filterExpressionBuilder.Append(GetFilter(searchingFilter.SearchingName, searchingFilter.SearchingOperator, searchingFilter.SearchingValue));
                        filterExpressionBuilder.Append(String.Format(" {0} ", groupingOperatorToString));
                    }
                    if (filterExpressionBuilder.Length > 0)
                        filterExpressionBuilder.Remove(filterExpressionBuilder.Length - groupingOperatorToString.Length - 2, groupingOperatorToString.Length + 2);
                    filterExpression = filterExpressionBuilder.ToString();
                }
                else if (viewModel != null)
                    filterExpression = viewModel.GetFilterExpression();
            }

            //Required workaround for grouping (it passes column name instead of index)
            string sortingName = request.SortingName.Replace("Category", "CategoryId");

            int totalRecordsCount = _productsRepository.GetCount(filterExpression);

            JqGridResponse response = new JqGridResponse()
            {
                TotalPagesCount = (int)Math.Ceiling((float)totalRecordsCount / (float)request.RecordsCount),
                PageIndex = request.PageIndex,
                TotalRecordsCount = totalRecordsCount,
                //Footer data
                UserData = new { QuantityPerUnit = "Avg Unit Price:", UnitPrice = _productsRepository.Find(filterExpression).Average(p => p.UnitPrice) }
            };
            response.Records.AddRange(from product in _productsRepository.FindRange(filterExpression, String.Format("{0} {1}", sortingName, request.SortingOrder), request.PageIndex * request.RecordsCount, (request.PagesCount.HasValue ? request.PagesCount.Value : 1) * request.RecordsCount)
                                      select new JqGridRecord<ProductFormattedViewModel>(Convert.ToString(product.Id), new ProductFormattedViewModel(product)));

            return new JqGridJsonResult() { Data= response };
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult InsertProduct(ProductInlineEditableViewModel viewModel)
        {
            if (TryValidateModel(viewModel))
            {
                try
                {
                    Product product = new Product();
                    product.Name = viewModel.Name;
                    product.SupplierId = viewModel.SupplierId;
                    product.CategoryId = viewModel.CategoryId;
                    product.QuantityPerUnit = viewModel.QuantityPerUnit;
                    product.UnitPrice = viewModel.UnitPrice;
                    product.UnitsInStock = viewModel.UnitsInStock;

                    _productsRepository.Add(ref product);
                    _productsRepository.SaveChanges();

                    return new HttpStatusCodeResult(200, "Succeeded");
                }
                catch
                {
                    //There should be some dicent exception handling here
                    return new HttpStatusCodeResult(500, "An error occurred during product insert.");
                }
            }
            else
                return new HttpStatusCodeResult(500, "Invalid product data.");
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult UpdateProduct(ProductInlineEditableViewModel viewModel)
        {
            if (TryValidateModel(viewModel))
            {
                try
                {
                    Product product = _productsRepository.FindByKey(viewModel.Id);
                    product.Name = viewModel.Name;
                    product.SupplierId = viewModel.SupplierId;
                    product.CategoryId = viewModel.CategoryId;
                    product.QuantityPerUnit = viewModel.QuantityPerUnit;
                    product.UnitPrice = viewModel.UnitPrice;
                    product.UnitsInStock = viewModel.UnitsInStock;

                    _productsRepository.SaveChanges();

                    return new HttpStatusCodeResult(200, "Succeeded");
                }
                catch
                {
                    //There should be some dicent exception handling here
                    return new HttpStatusCodeResult(500, "An error occurred during product update.");
                }
            }
            else
                return Json(false);
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult UpdateUnitsInStock(int id, short unitsInStock)
        {
            Product product = _productsRepository.FindByKey(id);
            product.UnitsInStock = unitsInStock;

            try
            {
                _productsRepository.SaveChanges();
            }
            catch
            {
                return new HttpStatusCodeResult(500, "An error occurred during product update.");
            }

            return new HttpStatusCodeResult(200, "Succeeded");
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult DeleteProduct(int id)
        {
            try
            {
                _productsRepository.Delete(id);
                _productsRepository.SaveChanges();

                return new HttpStatusCodeResult(200, "Succeeded");
            }
            catch
            {
                //There should be some dicent exception handling here
                return new HttpStatusCodeResult(500, "An error occurred during product delete.");
            }
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Supplier(int id)
        {
            Supplier supplier = _suppliersRepository.FindByKey(id);

            var supplierData = new
            {
                CompanyName = supplier.Name,
                Address = supplier.Address,
                PostalCode = supplier.PostalCode,
                City = supplier.City,
                Country = supplier.Country,
                Phone = supplier.Phone,
                HomePage = supplier.HomePage
            };

            return Json(supplierData);
        }

        public ActionResult Suppliers()
        {
            Dictionary<int, string> suppliers = new Dictionary<int, string>();
            foreach (Supplier supplier in _suppliersRepository.FindAll())
                suppliers.Add(supplier.Id, supplier.Name);
            return PartialView("Select", suppliers);
        }

        public ActionResult Categories()
        {
            Dictionary<int, string> categories = new Dictionary<int, string>();
            foreach (Category category in _categoriesRepository.FindAll())
                categories.Add(category.Id, category.Name);
            return PartialView("Select", categories);
        }

        public ActionResult ProductsNames(string term)
        {
            return Json(_productsRepository.Find(String.Format("Name.StartsWith(\"{0}\")", term)).Select(p => p.Name).ToArray(), JsonRequestBehavior.AllowGet);
        }

        public ActionResult Subgrid()
        {
            return View();
        }

        public ActionResult SubgridAsGrid()
        {
            return View();
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Orders(JqGridRequest request)
        {
            int totalRecords = _ordersRepository.GetCount();

            JqGridResponse response = new JqGridResponse()
            {
                TotalPagesCount = (int)Math.Ceiling((float)totalRecords / (float)request.RecordsCount),
                PageIndex = request.PageIndex,
                TotalRecordsCount = totalRecords
            };
            response.Records.AddRange(from order in _ordersRepository.FindRange(String.Format("{0} {1}", request.SortingName, request.SortingOrder), request.PageIndex * request.RecordsCount, request.RecordsCount)
                                      select new JqGridRecord<OrderViewModel>(Convert.ToString(order.Id), new OrderViewModel(order)));
            
            return new JqGridJsonResult() { Data = response };
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult OrderDetailsSubgrid(int id)
        {
            JqGridResponse response = new JqGridResponse(true);
            response.Records.AddRange(from OrderDetail orderDetails in _ordersRepository.FindDetails(id)
                                      select new JqGridRecord<OrderDetailViewModel>(null, new OrderDetailViewModel(orderDetails)));

            return new JqGridJsonResult() { Data = response };
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult OrderDetailsSubgridAsGrid(int id, JqGridRequest request)
        {
            IEnumerable<OrderDetail> details = _ordersRepository.FindDetails(id);
            int totalRecords = details.Count();

            JqGridResponse response = new JqGridResponse()
            {
                TotalPagesCount = (int)Math.Ceiling((float)totalRecords / (float)request.RecordsCount),
                PageIndex = request.PageIndex,
                TotalRecordsCount = totalRecords
            };

            response.Records.AddRange(from OrderDetail orderDetails in details.Skip(request.PageIndex * request.RecordsCount).Take(request.RecordsCount)
                                      select new JqGridRecord<OrderDetailViewModel>(orderDetails.ProductId.ToString(), new OrderDetailViewModel(orderDetails)));

            return new JqGridJsonResult() { Data = response };
        }

        public ActionResult TreeGrid()
        {
            return View();
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult FileSystemInfos(int? nodeid)
        {

            DirectoryInfo root = FileSystemInfosRepository.GetDirectoryInfo(nodeid);
            IEnumerable<FileSystemInfo> children = FileSystemInfosRepository.GetFileSystemInfos(nodeid);

            if (children != null)
            {
                JqGridResponse response = new JqGridResponse();
                response.Records.AddRange(from child in children
                                          select new JqGridAdjacencyTreeRecord<FileSystemInfoViewModel>(
                                              Convert.ToString(FileSystemInfosRepository.GetTreeGridId(child)),
                                              new FileSystemInfoViewModel(child),
                                              ((from backslash in child.FullName where backslash == '\\' select backslash).Count() - 2),
                                              Convert.ToString(FileSystemInfosRepository.GetTreeGridId(root))) { Leaf = (child is FileInfo) });
                
                return new JqGridJsonResult() { Data = response };
            }
            else
                return new EmptyResult();
        }
        #endregion

        #region Methods
        private string GetFilter(string searchingName, JqGridSearchOperators searchingOperator, string searchingValue)
        {
            string searchingOperatorString = String.Empty;
            switch (searchingOperator)
            {
                case JqGridSearchOperators.Eq:
                    searchingOperatorString = "=";
                    break;
                case JqGridSearchOperators.Ne:
                    searchingOperatorString = "!=";
                    break;
                case JqGridSearchOperators.Lt:
                    searchingOperatorString = "<";
                    break;
                case JqGridSearchOperators.Le:
                    searchingOperatorString = "<=";
                    break;
                case JqGridSearchOperators.Gt:
                    searchingOperatorString = ">";
                    break;
                case JqGridSearchOperators.Ge:
                    searchingOperatorString = ">=";
                    break;
            }

            searchingName = searchingName.Replace("Category", "CategoryId");
            if ((searchingName == "Id") || (searchingName == "SupplierId") || (searchingName == "CategoryId"))
                return String.Format("{0} {1} {2}", searchingName, searchingOperatorString, searchingValue);

            if ((searchingName == "Name"))
            {
                switch (searchingOperator)
                {
                    case JqGridSearchOperators.Bw:
                        return String.Format("{0}.StartsWith(\"{1}\")", searchingName, searchingValue);
                    case JqGridSearchOperators.Bn:
                        return String.Format("!{0}.StartsWith(\"{1}\")", searchingName, searchingValue);
                    case JqGridSearchOperators.Ew:
                        return String.Format("{0}.EndsWith(\"{1}\")", searchingName, searchingValue);
                    case JqGridSearchOperators.En:
                        return String.Format("!{0}.EndsWith(\"{1}\")", searchingName, searchingValue);
                    case JqGridSearchOperators.Cn:
                        return String.Format("{0}.Contains(\"{1}\")", searchingName, searchingValue);
                    case JqGridSearchOperators.Nc:
                        return String.Format("!{0}.Contains(\"{1}\")", searchingName, searchingValue);
                    default:
                        return String.Format("{0} {1} \"{2}\"", searchingName, searchingOperatorString, searchingValue);
                }
            }

            return String.Empty;
        }
        #endregion
    }
}
